﻿#define Fun

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Lesson18
{
    // 特性 类似java中的注解
    #region 特性
    // 特性是什么
    // 特性是一种允许我们向程序的程序集添加元数据的语言结构
    // 它用于保存程序结构信息的某种特殊类型的类
    // 特性提供功能强大的方法以将声明信息与 c# 代码（类型、方法、属性等）相关联
    // 特性与程序实体关联后，即可在运行时使用反射查询特性信息

    // 特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集中
    // 它可以放置在几乎所有的声明中（类、变量、函数 等等）

    // 特性的本质是个类
    // 我们可以利用特性类为元数据添加额外的信息
    // 之后可以通过反射来获取这些信息


    // 自定义特性
    // 继承特性基类 Attribute
    class MyCustomAttribute : Attribute
    {
        // 特性类的构造函数
        public MyCustomAttribute(string name, string info)
        {
            Name = name;
            this.info = info;
        }
        // 特性类的属性(成员)
        public string Name { get; set; }

        // 特性中的成员
        public string info;
    }
    class MyCustom2Attribute : Attribute
    {
        // 特性类的构造函数
        public MyCustom2Attribute(string name)
        {
            Name = name;
        }
        // 特性类的属性(成员)
        public string Name { get; set; }

        // 特性中的成员
        public string info;
    }
    class MyCustom3Attribute : Attribute
    {
        // 特性类的构造函数
        public MyCustom3Attribute(string info)
        {
            this.info = info;
        }

        // 特性中的成员
        public string info;

        public void testFun()
        {

        }
    }

    // 特性的使用
    // 基本语法： [特性名(参数列表)]
    // 本质上就是在调用特性类的构造函数
    // 写在哪里？ 写在 类、函数、变量等声明的上一行，表示它们具有该特性
    //[MyCustomAttribute("MyClass")]  // Attribute可以省略
    //[MyCustom("MyClass")]
    //[MyCustom("MyClass", "这是一个自己写的用于xxx的类")] 
    //[MyCustom2("MyClass", info = "这是一个自己写的用于xxx的类")]   // 特性类的构造函数不赋值，可以这样传
    [MyCustom3("这是一个自己写的用于xxx的类")]
    class MyClass
    {
        [MyCustom3("成员变量，一个int类型的值")]
        public int value;

        [MyCustom3("用于相加计算的方法")]
        public void TestAdd([MyCustom3("一个固定的参数")]int a)
        {

        }
    }

    // 限制自定义特性的使用范围
    // 通过为特性类加上 特性 可以限制其使用范围
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
    // 参数1： AttributeTargets  -- 特性可以用在哪些地方
    // 参数2：AllowMultiple -- 是否允许多个特性实例用在同一个目标上
    // 参数3：Inherited -- 特性是否能被派生类和重写成员继承
    public class MyCustom4Attribute : Attribute
    {

    }
    // AttributeTargets 有很多值 是一个枚举类型
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
    public class MyCustom5A : Attribute
    {

    }

    #endregion

    #region 系统自带特性
    // 过时特性
    // Obsolete 过时的  一般加在函数前的特性
    // 用于提示用户 使用的方法等成员已经过时，建议使用新方法
    class TestClass
    {
        // 参数1：提示信息
        // 参数2： 如果是true 时，调用该方法时直接会报错， false 使用该方法时会报警告
        //[Obsolete("方法已经过时，使用新方法Speak", true)]
        [Obsolete("方法已经过时，使用新方法Speak", false)]
        public void OldSpeak(string str) 
        { 

        }

        public void Speak(string str)
        {

        }

        // 这样会报错，必须要有默认值，否则就是传值的
        //public void SpeakCaller(string str, [CallerFilePath] string fileName, [CallerLineNumber] int line, [CallerMemberName] string target)
        //{

        //}

        // 当有默认值时，调用方法时又不传参，就会根据这些特性会自动传入检测到的值
        public void SpeakCaller(string str, [CallerFilePath]string fileName = "", [CallerLineNumber]int line = 0, [CallerMemberName]string target = "")
        {
            Console.WriteLine(str);
            Console.WriteLine(fileName);
            Console.WriteLine(line);
            Console.WriteLine(target);
        }
        
    }


    // 调用者信息特性
    // 哪个文件调用？  CallerFilePath 特性
    // 哪一行调用？ CallerLineNumber 特性
    // 哪个函数调用？ CallerMemberName 特性
    // 需要引用命名空间 System.Runtime.CompilerServices;
    // 一般作为函数参数的特性， 如上例


    // 条件编译特性
    // Conditional
    // 它会和预处理指令 #define配合使用
    // 引入命名空间System.Diagnostics
    // 主要用在一些调试代码上 有时要执行有时不要执行的代码


    // 外部Dll包函数特性
    // DllImport
    // 用来标记非 .Net（c#）的函数，表明该函数在一个外部的Dll中定义
    // 一般用来调用 c 或者c++ 的Dll包写好的方法
    //[DllImport("要引用的Dll包的名字或路径")]

    #endregion

    internal class Program
    {

        [DllImport("key.dll")]
        public static extern int Add(int a, int b);


        [Conditional("Fun")]  // 这里设置特性后，使用#define定义加上这个条件后才会去编译 不然不去编译（调用无法执行）
        static void Fun()
        {
            Console.WriteLine("Fun执行");
        }

        static void Main(string[] args)
        {
            #region 特性的使用，反射得
            MyClass myClass = new MyClass();
            Type tt = myClass.GetType();

            // 判断是否使用了某个特性
            // 参数1：特性类型
            // 参数2：是否搜索继承链（属性和事件忽略此参数）
            if (tt.IsDefined(typeof(MyCustom3Attribute), false))
            {
                Console.WriteLine("该类型应用了MyCustom特性");
            }

            // 获取所有特性修饰的成员， 反射方式
            object[] array = tt.GetCustomAttributes(true);
            for (int i = 0; i < array.Length; i++)
            {
                //Console.WriteLine(array[i].ToString());
                if (array[i] is MyCustom3Attribute)
                {
                    string info = (array[i] as MyCustom3Attribute).info;  // 获取特性中属性
                    Console.WriteLine(info);
                    (array[i] as MyCustom3Attribute).testFun();  // 调用特性中方法
                }
            }


            // 
            TestClass ts = new TestClass();
            ts.OldSpeak("ff");


            ts.SpeakCaller("fulenile");

            Fun();  // 上面加上#define Fun    Fun函数才会被执行

            #endregion
        }
    }
}
